/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// common.c -- misc functions used in client and server

#include "quakedef.h"


bool FS_ContentListSortFunc (char *a, char *b)
{
	return strcmp (a, b) < 0;
}


class fs_contentlist_t
{
private:
	std::vector<char *> Items;

public:
	BOOL StripExtension;

	fs_contentlist_t (BOOL stripext)
	{
		this->Items.clear ();
		this->StripExtension = stripext;
	}

	char *RunAutoTab (key_autotab_t *at, char *partial, int len)
	{
		if (at->StartMatch)
		{
			// build a full list of all partial matches
			Key_ResetAutoTab (at, FALSE);

			for (int j = 0; j < this->Items.size (); j++)
			{
				if (strnicmp (this->Items[j], &partial[len], strlen (&partial[len]))) continue;

				Key_AddToAutoTab (at, j);
			}
		}

		if (at->NumMatches)
		{
			// run the autotab
			char *thisitem = this->Items[Key_RunAutoTab (at)];
			static char matched[1024];

			// compose the full command-line
			strcpy (matched, partial);
			strcpy (&matched[len], thisitem);

			// and return what we got
			return matched;
		}

		return NULL;
	}

	void AddItem (char *item)
	{
		this->Items.push_back (CopyString (item));
	}

	void EmptyList (void)
	{
		// empty the list
		for (int i = 0; i < this->Items.size (); i++)
		{
			if (this->Items[i])
			{
				zoneHeap->Free (this->Items[i]);
				this->Items[i] = NULL;
			}
		}

		this->Items.clear ();
	}

	void Sort (void)
	{
		// sort the list if required
		if (this->Items.size () < 2) return;
		std::sort (this->Items.begin (), this->Items.end (), FS_ContentListSortFunc);
	}
};


fs_contentlist_t fs_maplist (TRUE);
fs_contentlist_t fs_demolist (TRUE);
fs_contentlist_t fs_savelist (TRUE);
fs_contentlist_t fs_gamelist (TRUE);
fs_contentlist_t fs_execlist (FALSE);


bool FS_CheckKnownContent (char *mask)
{
	WIN32_FIND_DATA FindFileData;
	HANDLE hFind = FindFirstFile (mask, &FindFileData);

	if (hFind == INVALID_HANDLE_VALUE)
	{
		// found no files
		FindClose (hFind);
		return false;
	}

	// found something
	FindClose (hFind);
	return true;
}


bool FS_IsGameDir (char *path)
{
	char *basedir = host_parms.basedir;

	// check for known files that indicate a gamedir
	if (FS_CheckKnownContent (va ("%s/pak0.pak", path))) return true;
	if (FS_CheckKnownContent (va ("%s/config.cfg", path))) return true;
	if (FS_CheckKnownContent (va ("%s/autoexec.cfg", path))) return true;
	if (FS_CheckKnownContent (va ("%s/progs.dat", path))) return true;
	if (FS_CheckKnownContent (va ("%s/gfx.wad", path))) return true;

	// some gamedirs just have maps or models, or may have weirdly named paks
	if (FS_CheckKnownContent (va ("%s/%s/maps/*.bsp", basedir, path))) return true;
	if (FS_CheckKnownContent (va ("%s/%s/progs/*.mdl", basedir, path))) return true;
	if (FS_CheckKnownContent (va ("%s/%s/*.pak", basedir, path))) return true;

	// some gamedirs are just used for keeping stuff separate
	if (FS_CheckKnownContent (va ("%s/%s/*.sav", basedir, path))) return true;
	if (FS_CheckKnownContent (va ("%s/%s/*.dem", basedir, path))) return true;

	// nope
	return false;
}


void FS_EnumGameDirs (fs_contentlist_t *list)
{
	list->EmptyList ();

	WIN32_FIND_DATA FindFileData;
	HANDLE hFind = FindFirstFile ("*", &FindFileData);

	if (hFind == INVALID_HANDLE_VALUE)
	{
		// found no files
		FindClose (hFind);
		Sys_Error ("EnumGameDirs: Empty Quake Folder!");
		return;
	}

	do
	{
		// not interested
		if (!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) continue;
		if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) continue;

		// don't do these
		if (!strcmp (FindFileData.cFileName, ".")) continue;
		if (!strcmp (FindFileData.cFileName, "..")) continue;

		// ensure that it's a game dir
		if (!FS_IsGameDir (FindFileData.cFileName)) continue;

		// store it out
		list->AddItem (FindFileData.cFileName);
	} while (FindNextFile (hFind, &FindFileData));

	// close the finder
	FindClose (hFind);

	// and sort the list
	list->Sort ();
}


void FS_CheckAddItem (fs_contentlist_t *list, char *path, char *ext, char *item)
{
	// not in the path
	if (strnicmp (item, path, strlen (path))) return;

	for (int i = strlen (item); i; i--)
	{
		// if we hit either of these we haven't found an extension
		if (item[i] == '/') return;
		if (item[i] == '\\') return;

		// here we've found the extension
		if (item[i] == '.')
		{
			// extension doesn't match
			if (stricmp (&item[i], ext)) return;

			// copy it off so that we can safely modify it
			char tempitem[MAX_PATH];

			strcpy (tempitem, item);

			// optionally remove the extension
			if (list->StripExtension) tempitem[i] = 0;

			// and add it to the list
			list->AddItem (&tempitem[strlen (path)]);

			// and done
			return;
		}
	}

	// if we hit here we've found nothing
}


void FS_EnumerateItem (fs_contentlist_t *list, char *path, char *ext, BOOL gamedir_only = FALSE)
{
	list->EmptyList ();

	for (searchpath_t *search = com_searchpaths; search; search = search->next)
	{
		if (search->pack)
		{
			pack_t *pak = search->pack;

			for (int i = 0; i < pak->numfiles; i++)
				FS_CheckAddItem (list, path, ext, pak->files[i].name);

			continue;
		}

		// check in filesystem
		if (gamedir_only)
			if (stricmp (com_gamedir, search->filename))
				continue;

		WIN32_FIND_DATA FindFileData;
		HANDLE hFind = FindFirstFile (va ("%s/%s*%s", search->filename, path, ext), &FindFileData);

		if (hFind == INVALID_HANDLE_VALUE)
		{
			// found no files
			FindClose (hFind);
		}
		else
		{
			// add all the matching files
			do
			{
				// not interested
				if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
				if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) continue;

				// don't do these
				if (!strcmp (FindFileData.cFileName, ".")) continue;
				if (!strcmp (FindFileData.cFileName, "..")) continue;

				FS_CheckAddItem (list, path, ext, va ("%s%s", path, FindFileData.cFileName));
			} while (FindNextFile (hFind, &FindFileData));

			// close the finder
			FindClose (hFind);
		}
	}

	// and sort the list
	list->Sort ();
}


void FS_EnumerateMaps (void) {FS_EnumerateItem (&fs_maplist, "maps/", ".bsp");}
void FS_EnumerateDemos (void) {FS_EnumerateItem (&fs_demolist, "", ".dem");}
void FS_EnumerateSaves (void) {FS_EnumerateItem (&fs_savelist, "save/", ".sav", TRUE);}
void FS_EnumerateCfgs (void) {FS_EnumerateItem (&fs_execlist, "", ".cfg");}

void FS_EnumerateContent (void)
{
	// enumerate all of our content items
	FS_EnumerateMaps ();
	FS_EnumerateDemos ();
	FS_EnumerateSaves ();
	FS_EnumerateCfgs ();

	// also enum the gamedirs here too
	FS_EnumGameDirs (&fs_gamelist);
}


char *FS_AutoCompleteList (key_autotab_t *at, char *partial)
{
	if (!partial) return NULL;
	if (!partial[0]) return NULL;

	// easy list of stuff we want to match and their associated lists
	struct key_autotablist_t {char *str; fs_contentlist_t *list;} key_autotablist[] =
	{
		{"map ", &fs_maplist},
		{"changelevel ", &fs_maplist},
		{"playdemo ", &fs_demolist},
		{"timedemo ", &fs_demolist},
		{"save ", &fs_savelist},
		{"load ", &fs_savelist},
		{"game ", &fs_gamelist},
		{"gamedir ", &fs_gamelist},
		{"exec ", &fs_execlist},
		{NULL, NULL}
	};

	// see which list we're going to auto-complete
	for (int i = 0; ; i++)
	{
		if (!key_autotablist[i].str) break;
		if (!key_autotablist[i].list) break;

		fs_contentlist_t *list = key_autotablist[i].list;
		int len = strlen (key_autotablist[i].str);

		if (!strnicmp (partial, key_autotablist[i].str, len))
		{
			return list->RunAutoTab (at, partial, len);
		}
	}

	// not matched
	return NULL;
}


